package cn.melonlib.code.generator.comp.creator;

import cn.hutool.core.util.ReflectUtil;
import cn.melonlib.code.generator.comp.service.WorkerServiceDescriptionService;
import cn.melonlib.code.generator.model.entity.*;
import cn.melonlib.code.generator.model.enums.WorkerType;
import cn.melonlib.cores.commons.comp.service.impl.WorkerBasicService;
import cn.melonlib.cores.jpa.model.bean.collections.JsonMap;
import cn.melonlib.cores.model.input.OrderByInput;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.CaseFormat;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.output.FileWriterWithEncoding;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.stereotype.Service;
import org.springframework.util.ClassUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import javax.annotation.Resource;
import java.io.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
public class TypeScriptCreator extends ApplicationObjectSupport {

    @Resource
    private WorkerServiceDescriptionService workerServiceDescriptionService;

    @Resource
    private FreeMarkerConfigurer freemarkerConfig;

    @Value("${apps.client.typescript.source:/src/app/main/graphql}")
    private String rootPath;

    @Value("${apps.client.typescript.exportPath:./app/main/graphql}")
    private String exportRootPath;
    @Value("${apps.client.typescript.exportFilePath:/src/public-api.ts}")
    private String exportFileName;


    @Value("${system.config.name:root}")
    private String root;
    public void createTypeScriptFile(String appPath, Class<? extends WorkerBasicService<?>> workerService,Boolean createService) throws Exception{
        this.createTypeScriptFile(appPath,workerService,createService,false);
    }

    public void cleanExports(String appPath){
        FileUtils.deleteQuietly(new File(appPath + exportFileName));
    }

    public void createTypeScriptFile(String appPath, Class<? extends WorkerBasicService<?>> workerService,Boolean createService,boolean library) throws Exception {
        logger.info("class:"+workerService.getName());
        BeansWrapper wrapper = new BeansWrapperBuilder(Configuration.getVersion()).build();
        WorkerServiceMap serviceInfo = workerServiceDescriptionService.loadMap(workerService);
        Template declareTpl = freemarkerConfig.getConfiguration().getTemplate("service_schemas/declare.ts", "utf-8");
        String servicePath= CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE,serviceInfo.getModelName());
        String fileName= CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE,serviceInfo.getBeanName());
        File declareFile=new File(appPath + rootPath+"/"+root+"/"+serviceInfo.getModule()+"/"+servicePath+"/model/" + fileName + ".model.ts");
        FileUtils.forceMkdirParent(declareFile);
        FileUtils.deleteQuietly(declareFile);
        ObjectMapper mapper=new ObjectMapper();
//        logger.debug(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(serviceInfo));
        JsonMap params=JsonMap.createMap().append("root",root).append("importTypes",workerServiceDescriptionService.dependenciesTypes(workerService)).append("serviceInfo", serviceInfo).append("typeMap", new TemplateMethodModelEx() {
            public Object exec(List list) throws TemplateModelException {
                try {
                    WorkerServiceField field = (WorkerServiceField) wrapper.unwrap((TemplateModel) list.get(0));
                    switch (field.getTypeName()) {
                        case "java.lang.String":
                            return "string";
                        case "java.lang.Long":
                            return "number";
                        case "java.lang.Integer":
                            return "number";
                        case "java.lang.Double":
                            return "number";
                        case "java.lang.Boolean":
                            return "boolean";
                        default: {
                            switch (field.getWorkerType()) {
                                case ENUMS: {
                                    Method valuesMethod = ClassUtils.forName(field.getTypeName(), null).getMethod("values");
                                    Enum[] es = ReflectUtil.invokeStatic(valuesMethod);
                                    return Arrays.stream(es).map(e->"'"+e.name()+"'").collect(Collectors.joining(" | "));
                                }
                                case LIST_ENUMS: {
                                    Method valuesMethod = ClassUtils.forName(field.getTypeName(), null).getMethod("values");
                                    Enum[] es = ReflectUtil.invokeStatic(valuesMethod);
                                    return Arrays.stream(es).map(e->"'"+e.name()+"'[]").collect(Collectors.joining(" | "));
                                }
                                case STORE_ENTITY:
                                    return "string";
                                case VIEW_ENTITY:
                                    return "string";
                                case INPUT_ENTITY:
                                    return "string";
                                case FILTER_ENTITY:
                                    return "string";
                                case LIST_STRING:
                                    return "string[]";
                                case LIST_LONG:
                                    return "number[]";
                                case LIST_INT:
                                    return "number[]";
                                case LIST_DOUBLE:
                                    return "number[]";
                                case API_ENTITY:
                                    return ClassUtils.forName(field.getTypeName(), null).getSimpleName();
                                case LIST_API_ENTITY:
                                    return ClassUtils.forName(field.getTypeName(), null).getSimpleName() + "[]";
                                default:
                                    return "string";
                            }
                        }
                    }
                } catch (Exception e) {
                    throw new TemplateModelException(e);
                }
            }
        }).append("filterMap", new TemplateMethodModelEx() {
            public Object exec(List arguments) throws TemplateModelException {
                try {
                    WorkerServiceField field = (WorkerServiceField) wrapper.unwrap((TemplateModel) arguments.get(0));
                    switch (field.getTypeName()) {
                        case "java.lang.Long":
                            return "NumberFilter";
                        case "java.lang.Integer":
                            return "NumberFilter";
                        case "java.lang.Double":
                            return "NumberFilter";
                        case "java.lang.Boolean":
                            return "BooleanFilter";
                        default:
                            return "StringFilter";
                    }
                } catch (Exception e) {
                    throw new TemplateModelException(e);
                }
            }
        }).append("typeSimpleName", new TemplateMethodModelEx() {
            public Object exec(List arguments) throws TemplateModelException {
                try {
                    return ClassUtils.forName(arguments.get(0).toString(), null).getSimpleName();
                } catch (Exception e) {
                    throw new TemplateModelException(e);
                }
            }
        }).append("paramMap", new TemplateMethodModelEx() {
            public Object exec(List arguments) throws TemplateModelException {
                try {
                    WorkerServiceParam param = (WorkerServiceParam) wrapper.unwrap((TemplateModel) arguments.get(0));
                    switch (param.getWorkerType()) {
                        case ENUMS: {
                            Method valuesMethod = ClassUtils.forName(param.getTypeName(), null).getMethod("values");
                            Enum[] es = ReflectUtil.invokeStatic(valuesMethod);
                            return Arrays.stream(es).map(e -> "'" + e.name() + "'").collect(Collectors.joining(" | "));
                        }
                        case LIST_ENUMS: {
                            Method valuesMethod = ClassUtils.forName(param.getTypeName(), null).getMethod("values");
                            Enum[] es = ReflectUtil.invokeStatic(valuesMethod);
                            return Arrays.stream(es).map(e -> "'" + e.name() + "'[]").collect(Collectors.joining(" | "));
                        }
                        case INT:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": number";
                        case LIST_INT:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": number[]";
                        case LONG:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": number";
                        case LIST_LONG:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": number[]";
                        case DOUBLE:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": number";
                        case LIST_DOUBLE:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": number[]";
                        case STRING:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": string";
                        case LIST_STRING:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": string[]";
                        case BOOLEAN:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": boolean";
                        case LIST_BOOLEAN:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": boolean[]";
                        case STORE_ENTITY:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": " + serviceInfo.getModelName();
                        case LIST_STORE_ENTITY:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": " + serviceInfo.getModelName() + "[]";
                        case INPUT_ENTITY:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": " + serviceInfo.getInputName();
                        case LIST_INPUT_ENTITY:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": " + serviceInfo.getInputName() + "[]";
                        case FILTER_ENTITY:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": " + serviceInfo.getFilterName();
                        case LIST_FILTER_ENTITY:
                            return param.getName() + (param.isRequired() ? "" : "?") + ": " + serviceInfo.getFilterName() + "[]";
                        default: {
                            try {
                                Class<?> cls = ClassUtils.forName(param.getTypeName(), null);
                                if (Objects.equals(cls, OrderByInput.class)) {
                                    return param.getName() + (param.isRequired() ? "" : "?") + ": " + serviceInfo.getModelName() + "OrderBy";
                                }
                                return param.getName() + (param.isRequired() ? "" : "?") + ": any";
                            } catch (Exception e) {
                                return param.getName() + (param.isRequired() ? "" : "?") + ": any";
                            }

                        }
                    }
                }catch (Exception e){
                    throw new TemplateModelException(e.getMessage(),e);
                }
            }
        }).append("returnMap", new TemplateMethodModelEx() {
            public Object exec(List arguments) throws TemplateModelException {
                try {
                    Object api = wrapper.unwrap((TemplateModel) arguments.get(0));
                    Object bean=getApplicationContext().getBean(serviceInfo.getBeanName());
                    if (api instanceof WorkerServiceMutationMap) {
                        return serviceInfo.getModelName() + "MutationInfo";
                    }
                    WorkerServiceQueryMap queryMap = (WorkerServiceQueryMap) api;
                    WorkerType workerType = queryMap.getResult();
                    switch (workerType) {
                        case ENUMS: {
                            Method m=bean.getClass().getMethod(queryMap.getName());
                            Method valuesMethod = m.getReturnType().getMethod("values");
                            Enum[] es = ReflectUtil.invokeStatic(valuesMethod);
                            return Arrays.stream(es).map(e -> "'" + e.name() + "'").collect(Collectors.joining(" | "));
                        }
                        case LIST_ENUMS: {
                            Method m=bean.getClass().getMethod(queryMap.getName());
                            Method valuesMethod = m.getReturnType().getMethod("values");
                            Enum[] es = ReflectUtil.invokeStatic(valuesMethod);
                            return Arrays.stream(es).map(e -> "'" + e.name() + "'[]").collect(Collectors.joining(" | "));
                        }
                        case INT:
                            return serviceInfo.getModelName()+"Number";
                        case LIST_INT:
                            return serviceInfo.getModelName()+"Number[]";
                        case LONG:
                            return serviceInfo.getModelName()+"Number";
                        case LIST_LONG:
                            return serviceInfo.getModelName()+"Number[]";
                        case DOUBLE:
                            return serviceInfo.getModelName()+"Number";
                        case LIST_DOUBLE:
                            return serviceInfo.getModelName()+"Number[]";
                        case STRING:
                            return serviceInfo.getModelName()+"String";
                        case LIST_STRING:
                            return serviceInfo.getModelName()+"String[]";
                        case BOOLEAN:
                            return serviceInfo.getModelName()+"Boolean";
                        case LIST_BOOLEAN:
                            return serviceInfo.getModelName()+"Boolean[]";
                        case STORE_ENTITY:
                            return serviceInfo.getModelName() + "Package";
                        case LIST_STORE_ENTITY:
                            return serviceInfo.getModelName() + "Connection";
                        case VIEW_ENTITY:
                            return serviceInfo.getModelName() + "Package";
                        case LIST_VIEW_ENTITY:
                            return serviceInfo.getModelName() + "Connection";
                        default:
                            return "any";
                    }
                }catch (Exception e){
                    throw new TemplateModelException(e.getMessage(),e);
                }
            }
        });
        {
            StringWriter sw = new StringWriter();
            try {
                declareTpl.process(params, sw);
            }catch (Exception e){

            }
            System.out.println(sw.toString());
        }
        try (PrintWriter writer = new PrintWriter(new FileWriterWithEncoding(declareFile, "utf-8"))) {
            declareTpl.process(params, writer);
        }
        if(createService){
            Template serviceTpl = freemarkerConfig.getConfiguration().getTemplate("service_schemas/service.ts", "utf-8");
            File serviceFile=new File(appPath + rootPath+"/"+root+"/"+serviceInfo.getModule()+"/"+servicePath+"/service/" + fileName + ".service.ts");
            FileUtils.forceMkdirParent(serviceFile);
            FileUtils.deleteQuietly(serviceFile);
            try (PrintWriter writer = new PrintWriter(new FileWriterWithEncoding(serviceFile, "utf-8"))) {
                serviceTpl.process(params.append("fileName",fileName).append("gqlParamMap", new TemplateMethodModelEx() {
                    public Object exec(List list) throws TemplateModelException {
                        WorkerServiceParam param = (WorkerServiceParam) wrapper.unwrap((TemplateModel) list.get(0));
                        switch (param.getWorkerType()) {
                            case INT:
                                return "$"+param.getName() + ": Int"+(param.isRequired()?"!":"");
                            case LIST_INT:
                                return "$"+param.getName() + ": [Int]"+(param.isRequired()?"!":"");
                            case LONG:
                                return "$"+param.getName() + ": Float"+(param.isRequired()?"!":"");
                            case LIST_LONG:
                                return "$"+param.getName() + ": [Float]"+(param.isRequired()?"!":"");
                            case DOUBLE:
                                return "$"+param.getName() + ": Float"+(param.isRequired()?"!":"");
                            case LIST_DOUBLE:
                                return "$"+param.getName() + ": [Float]"+(param.isRequired()?"!":"");
                            case STRING:
                                return "$"+param.getName() + ": String"+(param.isRequired()?"!":"");
                            case LIST_STRING:
                                return "$"+param.getName() + ": [String]"+(param.isRequired()?"!":"");
                            case BOOLEAN:
                                return "$"+param.getName() + ": Boolean"+(param.isRequired()?"!":"");
                            case LIST_BOOLEAN:
                                return "$"+param.getName() + ": [Boolean]"+(param.isRequired()?"!":"");
                            case STORE_ENTITY:
                                return "$"+param.getName() + ": "+serviceInfo.getModelName()+(param.isRequired()?"!":"");
                            case LIST_STORE_ENTITY:
                                return "$"+param.getName() + ": ["+serviceInfo.getModelName()+"]"+(param.isRequired()?"!":"");
                            case INPUT_ENTITY:
                                return "$"+param.getName() + ": "+serviceInfo.getInputName()+(param.isRequired()?"!":"");
                            case LIST_INPUT_ENTITY:
                                return "$"+param.getName() + ": ["+serviceInfo.getInputName()+"]"+(param.isRequired()?"!":"");
                            case FILTER_ENTITY:
                                return "$"+param.getName() + ": "+serviceInfo.getFilterName()+(param.isRequired()?"!":"");
                            case LIST_FILTER_ENTITY:
                                return "$"+param.getName() + ": ["+serviceInfo.getFilterName()+"]"+(param.isRequired()?"!":"");
                            default: {
                                try {
                                    Class<?> cls = ClassUtils.forName(param.getTypeName(), null);
                                    if (Objects.equals(cls, OrderByInput.class)) {
                                        return "$"+param.getName() + ": OrderBy"+(param.isRequired()?"!":"");
                                    }
                                    return "$"+param.getName() + ": String"+(param.isRequired()?"!":"");
                                } catch (Exception e) {
                                    return "$"+param.getName() + ": String"+(param.isRequired()?"!":"");
                                }
                            }
                        }
                    }
                }), writer);
            }
        }
        if(library){
            String modelExportPath=exportRootPath+"/"+root+"/"+serviceInfo.getModule()+"/"+servicePath+"/model/" + fileName + ".model";
            String serviceExportPath=exportRootPath+"/"+root+"/"+serviceInfo.getModule()+"/"+servicePath+"/service/" + fileName + ".service";
            Template exportTpl = freemarkerConfig.getConfiguration().getTemplate("service_schemas/export.ts", "utf-8");
            File serviceFile=new File(appPath + exportFileName);
            FileUtils.forceMkdirParent(serviceFile);
            try(BufferedWriter writer=new  BufferedWriter(new OutputStreamWriter(new FileOutputStream(serviceFile,true),"utf-8"))){
                StringWriter sw=new StringWriter();
                exportTpl.process(JsonMap.createMap().append("model",modelExportPath).append("service",serviceExportPath),sw);
                writer.append(sw.toString());
            }
        }
    }
}
